package gp;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Properties;
import java.util.Random;
import java.util.Vector;
import java.util.Collections;



/**
 * @author Beacom
 * @version 1.0
 * @created 18-Feb-2008 12:43:46 PM
 */
public class Trainer {

	// maxPopulation = trimSurvivorCount + crossoverCount + mutateCount
	private int maxPopulation; 
	private int crossoverCount;
	private int mutateCount;
	private int trimSurvivorCount; 
	/**
	 * must be less than or equal to trimSurvivorCount
	 */
	
	private int maxTreeHeight;
	
	private Vector<Organism> population = new Vector<Organism>();
	private DataSet trainingData;
	private String[] variables = {"x"};
	private static String prevExp= ""; // this is for writing a file.. we will only write if the new expression is different
	private ArrayList<Double>  xValue , yValue; // What are these for?
	
	public Trainer() {
		Properties config=new Properties();
		try {
			FileInputStream fis=new FileInputStream("settings.properties");
			config.load(fis);
			maxPopulation = Integer.parseInt(config.getProperty("maxPopulation"));
			trimSurvivorCount = Integer.parseInt(config.getProperty("trimSurvivorCount"));
			crossoverCount = Integer.parseInt(config.getProperty("crossoverCount"));
			mutateCount = Integer.parseInt(config.getProperty("mutateCount"));
			maxTreeHeight = Integer.parseInt(config.getProperty("maxTreeHeight"));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		generatePopulation();
	}

	public double evaluatePopulation() throws Exception {
		// This method evaluate and sorts the organism in population
		for(Organism o : population) {
			o.evaluate(trainingData);
		}
		Collections.sort(population);
		return population.get(0).getError(); // minimum error
	}

	public void generatePopulation() {
		int organismCreationCount = maxPopulation - population.size();
		for(int i = 0; i < organismCreationCount; i++) {
			Organism o = new Organism((new Random().nextInt(maxTreeHeight)+1),variables); // Here we will randomly creating population of different heights.
			population.add(o);
		}
	}
	
	/**
	 * 
	 * @param topOrganismsToCross
	 */
	public void crossover(){
		int popIndex = 0;
		for(int i = 0; i < crossoverCount; i++) {
			if(popIndex == trimSurvivorCount - 1) {
				popIndex = 0;
			}
			population.add(population.get(popIndex).crossover(population.get(popIndex + 1)));
			popIndex++;
		}
	}

	/**
	 * 
	 * @param topOrganismsToMutate
	 */
	public void mutate() {
		int popIndex = 0;
		for(int i = 0; i < mutateCount; i++) {
			if(popIndex == trimSurvivorCount) {
				popIndex = 0;
			}
			population.add(population.get(popIndex).mutate());
			popIndex++;
		}
	}

	/**
	 * 
	 * @param remainingPopulationCount
	 */
	public void trimPopulation(){
		while(population.size() > trimSurvivorCount) {
			population.removeElementAt(trimSurvivorCount);
		}
	}
	
	public void loadTrainingData() {
		
		loadTrainingDataFromFile();

		trainingData = new DataSet();
		DataEntry entry;
		for (int i=0; i < xValue.size(); i++){
			entry = new DataEntry();
			entry.setVariable(xValue.get(i), "x");
			entry.setOutput(yValue.get(i));
			trainingData.getDataEntryVector().add(entry);
		}
		/*for(double d = -20000.0; d < 21000.0; d += 1000.0) {
			entry = new DataEntry();
			entry.setVariable(d, "x");
			entry.setOutput((d*d-1)/2); // ((x*x) - 1) / 2
			trainingData.getDataEntryVector().add(entry);
		}*/
	}
	
	public String toString() {
		String output = "Max Population Size: " + this.maxPopulation + "\nMax Tree Height: " + this.maxTreeHeight + "\n";
		output += "Population Size: " + this.population.size() + "\n";
		for(Organism o : population) {
			output += o;
			output += "\n";
		}
		return output;
	}
	
	public static void main(String[] args) throws Exception {
		System.out.print("Initializing GP..."); 
		Trainer trainer = new Trainer();
		System.out.println("done.");
		System.out.println(trainer);
		trainer.loadTrainingData();
		int generation = 0;
		while(trainer.evaluatePopulation() > 1.0) {
			System.out.println("Generation " + generation);
			System.out.println("Error of best performing organism: " + trainer.population.get(0).getError());
			//System.out.println(trainer.population.get(0));
			trainer.writeToFile(trainer,generation);
			trainer.trimPopulation();
			trainer.crossover();
			trainer.mutate();
			trainer.generatePopulation();
			generation++;
		}
		
		System.out.println("Found Solution in generation " + generation + ": " + trainer.population.get(0) + ", Error: " + trainer.population.get(0).getError());
		trainer.writeToFile(trainer,generation);
	}
	
	public void loadTrainingDataFromFile() {
		File file=new File("training.data");
		BufferedReader in;
		try {
			in = new BufferedReader(new FileReader(file));
		String line;
		String temp[];// temp to hold the split
		
		xValue=new ArrayList<Double>();// to put x values
		yValue=new ArrayList<Double>();// to put y values
		
		// load all x and y values in an array list.
		while ((line=in.readLine())!=null)
		   {
		     temp=line.split(",");
		     xValue.add(Double.parseDouble(temp[0]));
		     yValue.add(Double.parseDouble(temp[1]));
		   }
		in.close();
		} catch (Exception e) { 
			e.printStackTrace();
		}
	}
	
	// This method should only write a file if it finds a different expression than previous.
	public void writeToFile(Trainer trainer, int gen) throws Exception {   
		Organism bestOrganism=trainer.population.get(0);
		
          // we will only write if the expression is different
		if (!prevExp.equals(bestOrganism.toString()))
		{
			prevExp=bestOrganism.toString();
			String FILE_NAME="gen";
			String filename=FILE_NAME+gen+".tmp";

			File file=new File(filename);
			BufferedWriter writer=new BufferedWriter(new FileWriter(file));
			writer.write(bestOrganism.toString());
			writer.newLine();
			writer.write(""+bestOrganism.getError());
			writer.newLine();
			
			//Add lines for X and Y's
			for (double i: xValue) {
				writer.write(""+i+", " + bestOrganism.evaluate(i));
				writer.newLine();
			}
		
			writer.close();
			
			File newName=new File(FILE_NAME+gen+".rpt");
			if(file.renameTo(newName))
				System.out.println("Copied OK");
			else
				System.out.println("Copy Failed.");
		}
	}

}